// Magic Software, Inc.
// http://www.magic-software.com
// Copyright (c) 2000, All Rights Reserved
//
// Source code from Magic Software is supplied under the terms of a license
// agreement and may not be copied or disclosed except in accordance with the
// terms of that agreement.  The various license agreements may be found at
// the Magic Software web site.  This file is subject to the license
//
// RESTRICTED USE SOURCE CODE
// http://www.magic-software.com/License/restricted.pdf

#include "MgcBound.h"
#include "MgcCamera.h"
#include "MgcRTLib.h"

MgcImplementRTTI(MgcCamera,MgcObject);
MgcImplementStream(MgcCamera);

//---------------------------------------------------------------------------
MgcCamera::MgcCamera ()
    :
    m_kLocation(MgcVector3::ZERO),
    m_kLeft(MgcVector3::UNIT_X),
    m_kUp(MgcVector3::UNIT_Y),
    m_kDirection(MgcVector3::UNIT_Z)
{
    m_fFrustumN = 1.0;
    m_fFrustumF = 2.0;
    m_fFrustumL = -0.5;
    m_fFrustumR = 0.5;
    m_fFrustumT = 0.5;
    m_fFrustumB = -0.5;
    m_fPortL = 0.0;
    m_fPortR = 1.0;
    m_fPortT = 1.0;
    m_fPortB = 0.0;
    m_uiPlaneState = (unsigned int)(~0);  // all planes initially active
    m_uiPlaneQuantity = 6;  // frustum planes always processed for culling

    OnFrustumChange();
    OnViewPortChange();
    OnFrameChange();
}
//---------------------------------------------------------------------------
void MgcCamera::SetFrustum (MgcReal fNear, MgcReal fFar, MgcReal fLeft,
    MgcReal fRight, MgcReal fTop, MgcReal fBottom)
{
    m_fFrustumN = fNear;
    m_fFrustumF = fFar;
    m_fFrustumL = fLeft;
    m_fFrustumR = fRight;
    m_fFrustumT = fTop;
    m_fFrustumB = fBottom;
    OnFrustumChange();
}
//---------------------------------------------------------------------------
void MgcCamera::SetFrustumNear (MgcReal fNear)
{
    m_fFrustumN = fNear;
    OnFrustumChange();
}
//---------------------------------------------------------------------------
void MgcCamera::SetFrustumFar (MgcReal fFar)
{
    m_fFrustumF = fFar;
    OnFrustumChange();
}
//---------------------------------------------------------------------------
void MgcCamera::GetFrustum (MgcReal& rfNear, MgcReal& rfFar,
    MgcReal& rfLeft, MgcReal& rfRight, MgcReal& rfTop,
    MgcReal& rfBottom) const
{
    rfNear = m_fFrustumN;
    rfFar = m_fFrustumF;
    rfLeft = m_fFrustumL;
    rfRight = m_fFrustumR;
    rfTop = m_fFrustumT;
    rfBottom = m_fFrustumB;
}
//---------------------------------------------------------------------------
MgcReal MgcCamera::GetMaxCosSqrFrustumAngle () const
{
    // Compute (cos(A))^2 where A is the largest angle between the frustum
    // axis direction D and the four edges of the frustum that lie along the
    // rays from the frustum origin.

    MgcReal fNSqr = m_fFrustumN*m_fFrustumN;

    MgcReal fDenom = fNSqr;
    if ( MgcMath::Abs(m_fFrustumL) >= MgcMath::Abs(m_fFrustumR) )
    {
        fDenom += m_fFrustumL*m_fFrustumL;
        if ( MgcMath::Abs(m_fFrustumB) >= MgcMath::Abs(m_fFrustumT) )
            fDenom += m_fFrustumB*m_fFrustumB;
        else
            fDenom += m_fFrustumT*m_fFrustumT;
    }
    else
    {
        fDenom += m_fFrustumR*m_fFrustumR;
        if ( MgcMath::Abs(m_fFrustumB) >= MgcMath::Abs(m_fFrustumT) )
            fDenom += m_fFrustumB*m_fFrustumB;
        else
            fDenom += m_fFrustumT*m_fFrustumT;
    }

    return fNSqr/fDenom;
}
//---------------------------------------------------------------------------
void MgcCamera::SetViewPort (MgcReal fLeft, MgcReal fRight, MgcReal fTop,
    MgcReal fBottom)
{
    m_fPortL = fLeft;
    m_fPortR = fRight;
    m_fPortT = fTop;
    m_fPortB = fBottom;
    OnViewPortChange();
}
//---------------------------------------------------------------------------
void MgcCamera::GetViewPort (MgcReal& rfLeft, MgcReal& rfRight,
    MgcReal& rfTop, MgcReal& rfBottom)
{
    rfLeft = m_fPortL;
    rfRight = m_fPortR;
    rfTop = m_fPortT;
    rfBottom = m_fPortB;
}
//---------------------------------------------------------------------------
void MgcCamera::SetFrame (const MgcVector3& rkLocation,
    const MgcVector3& rkLeft, const MgcVector3& rkUp,
    const MgcVector3& rkDirection)
{
    m_kLocation = rkLocation;
    m_kLeft = rkLeft;
    m_kUp = rkUp;
    m_kDirection = rkDirection;
    OnFrameChange();
}
//---------------------------------------------------------------------------
void MgcCamera::SetFrame (const MgcVector3& rkLocation,
    const MgcMatrix3& rkAxes)
{
    m_kLocation = rkLocation;
    m_kLeft = rkAxes.GetColumn(0);
    m_kUp = rkAxes.GetColumn(1);
    m_kDirection = rkAxes.GetColumn(2);
    OnFrameChange();
}
//---------------------------------------------------------------------------
void MgcCamera::SetLocation (const MgcVector3& rkLocation)
{
    m_kLocation = rkLocation;
    OnFrameChange();
}
//---------------------------------------------------------------------------
void MgcCamera::SetAxes (const MgcVector3& rkLeft, const MgcVector3& rkUp,
    const MgcVector3& rkDirection)
{
    m_kLeft = rkLeft;
    m_kUp = rkUp;
    m_kDirection = rkDirection;
    OnFrameChange();
}
//---------------------------------------------------------------------------
void MgcCamera::SetAxes (const MgcMatrix3& rkAxes)
{
    m_kLeft = rkAxes.GetColumn(0);
    m_kUp = rkAxes.GetColumn(1);
    m_kDirection = rkAxes.GetColumn(2);
    OnFrameChange();
}
//---------------------------------------------------------------------------
void MgcCamera::OnFrustumChange ()
{
    MgcReal fNSqr = m_fFrustumN*m_fFrustumN;
    MgcReal fLSqr = m_fFrustumL*m_fFrustumL;
    MgcReal fRSqr = m_fFrustumR*m_fFrustumR;
    MgcReal fBSqr = m_fFrustumB*m_fFrustumB;
    MgcReal fTSqr = m_fFrustumT*m_fFrustumT;

    MgcReal fInvLength = 1.0/MgcMath::Sqrt(fNSqr + fLSqr);
    m_afCoeffL[0] = m_fFrustumN*fInvLength;
    m_afCoeffL[1] = -m_fFrustumL*fInvLength;

    fInvLength = 1.0/MgcMath::Sqrt(fNSqr + fRSqr);
    m_afCoeffR[0] = -m_fFrustumN*fInvLength;
    m_afCoeffR[1] = m_fFrustumR*fInvLength;

    fInvLength = 1.0/MgcMath::Sqrt(fNSqr + fBSqr);
    m_afCoeffB[0] = m_fFrustumN*fInvLength;
    m_afCoeffB[1] = -m_fFrustumB*fInvLength;

    fInvLength = 1.0/MgcMath::Sqrt(fNSqr + fTSqr);
    m_afCoeffT[0] = -m_fFrustumN*fInvLength;
    m_afCoeffT[1] = m_fFrustumT*fInvLength;
}
//---------------------------------------------------------------------------
void MgcCamera::OnViewPortChange ()
{
}
//---------------------------------------------------------------------------
void MgcCamera::OnFrameChange ()
{
    MgcReal fDdE = m_kDirection.Dot(m_kLocation);

    // left plane
    m_akWorldPlane[CAM_LEFT_PLANE].Normal() = m_afCoeffL[0]*m_kLeft +
        m_afCoeffL[1]*m_kDirection;
    m_akWorldPlane[CAM_LEFT_PLANE].Constant() =
        m_kLocation.Dot(m_akWorldPlane[CAM_LEFT_PLANE].Normal());

    // right plane
    m_akWorldPlane[CAM_RIGHT_PLANE].Normal() = m_afCoeffR[0]*m_kLeft +
        m_afCoeffR[1]*m_kDirection;
    m_akWorldPlane[CAM_RIGHT_PLANE].Constant() = 
        m_kLocation.Dot(m_akWorldPlane[CAM_RIGHT_PLANE].Normal());

    // bottom plane
    m_akWorldPlane[CAM_BOTTOM_PLANE].Normal() = m_afCoeffB[0]*m_kUp +
        m_afCoeffB[1]*m_kDirection;
    m_akWorldPlane[CAM_BOTTOM_PLANE].Constant() = 
        m_kLocation.Dot(m_akWorldPlane[CAM_BOTTOM_PLANE].Normal());

    // top plane
    m_akWorldPlane[CAM_TOP_PLANE].Normal() = m_afCoeffT[0]*m_kUp +
        m_afCoeffT[1]*m_kDirection;
    m_akWorldPlane[CAM_TOP_PLANE].Constant() = 
        m_kLocation.Dot(m_akWorldPlane[CAM_TOP_PLANE].Normal());

    // far plane
    m_akWorldPlane[CAM_FAR_PLANE].Normal() = -m_kDirection;
    m_akWorldPlane[CAM_FAR_PLANE].Constant() = -(fDdE + m_fFrustumF);

    // near plane
    m_akWorldPlane[CAM_NEAR_PLANE].Normal() = m_kDirection;
    m_akWorldPlane[CAM_NEAR_PLANE].Constant() = fDdE + m_fFrustumN;
}
//---------------------------------------------------------------------------
void MgcCamera::Update ()
{
    OnFrustumChange();
    OnViewPortChange();
    OnFrameChange();
}
//---------------------------------------------------------------------------
void MgcCamera::PushPlane (const MgcPlane& rkPlane)
{
    if ( m_uiPlaneQuantity < CAM_MAX_WORLD_PLANES )
        m_akWorldPlane[m_uiPlaneQuantity++] = rkPlane;
}
//---------------------------------------------------------------------------
void MgcCamera::PopPlane ()
{
    if ( m_uiPlaneQuantity > CAM_FRUSTUM_PLANES )
    {
        // frustum planes may not be removed from the stack
        m_uiPlaneQuantity--;
    }
}
//---------------------------------------------------------------------------
bool MgcCamera::Culled (const MgcBound& kWorldBound)
{
    // TO DO.  This code does plane-at-a-time culling.  It is possible that
    // the bounding sphere is outside the visibility region without being
    // totally outside one of the planes.  Change this to be an exact
    // intersection test between sphere and convex polyhedron.

    // start with last pushed plane (potentially the most restrictive plane)
    unsigned int uiP = m_uiPlaneQuantity - 1;
    unsigned int uiMask = 1 << uiP;

    unsigned int uiI;
    for (uiI = 0; uiI < m_uiPlaneQuantity; uiI++, uiP--, uiMask >>= 1)
    {
        if ( m_uiPlaneState & uiMask )
        {
            MgcPlane::Side eSide = kWorldBound.WhichSide(m_akWorldPlane[uiP]);

            if ( eSide == MgcPlane::NEGATIVE_SIDE )
            {
                // Object is on negative side.  Cull it.
                return true;
            }

            if ( eSide == MgcPlane::POSITIVE_SIDE )
            {
                // Object is on positive side of plane.  There is no need to
                // compare subobjects against this plane, so mark it as
                // inactive.
                m_uiPlaneState &= ~uiMask;
            }
        }
    }

    return false;
}
//---------------------------------------------------------------------------
bool MgcCamera::Culled (unsigned int uiVertexQuantity,
    const MgcVector3* akVertex, bool bIgnoreNearPlane)
{
    // TO DO.  This code does plane-at-a-time culling.  It is possible that
    // the convex polygon is outside the visibility region without being
    // totally outside one of the planes.  Change this to be an exact
    // intersection test between convex polygon and convex polyhedron.
    //
    // The Boolean variable bIgnoreNearPlane should be set to 'true' when
    // the test polygon is a portal.  This avoids the situation when the
    // portal is in the view pyramid (eye+left/right/top/bottom), but is
    // between the eye and near plane.  In such a situation you do not want
    // the portal system to cull the portal.  This situation typically occurs
    // when the camera moves through the portal from current region to
    // adjacent region.

    // start with last pushed plane (potentially the most restrictive plane)
    unsigned int uiP = m_uiPlaneQuantity - 1;
    for (unsigned int uiI = 0; uiI < m_uiPlaneQuantity; uiI++, uiP--)
    {
        MgcPlane& rkPlane = m_akWorldPlane[uiP];
        if ( bIgnoreNearPlane && uiP == CAM_NEAR_PLANE )
            continue;

        unsigned int uiV;
        for (uiV = 0; uiV < uiVertexQuantity; uiV++)
        {
            MgcPlane::Side eSide = rkPlane.WhichSide(akVertex[uiV]);
            if ( eSide != MgcPlane::NEGATIVE_SIDE )
            {
                // polygon is not totally outside this plane
                break;
            }
        }

        if ( uiV == uiVertexQuantity )
        {
            // polygon is totally outside this plane
            return true;
        }
    }

    return false;
}
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
// streaming
//---------------------------------------------------------------------------
MgcObject* MgcCamera::Factory (MgcStream& rkStream)
{
    MgcCamera* pkObject = new MgcCamera;
    MgcStream::Link* pkLink = new MgcStream::Link(pkObject);
    pkObject->Load(rkStream,pkLink);
    return pkObject;
}
//---------------------------------------------------------------------------
void MgcCamera::Load (MgcStream& rkStream, MgcStream::Link* pkLink)
{
    MgcObject::Load(rkStream,pkLink);

    // native data
    MgcStreamRead(rkStream,m_fFrustumN);
    MgcStreamRead(rkStream,m_fFrustumF);
    MgcStreamRead(rkStream,m_fFrustumL);
    MgcStreamRead(rkStream,m_fFrustumR);
    MgcStreamRead(rkStream,m_fFrustumT);
    MgcStreamRead(rkStream,m_fFrustumB);
    MgcStreamRead(rkStream,m_fPortL);
    MgcStreamRead(rkStream,m_fPortR);
    MgcStreamRead(rkStream,m_fPortT);
    MgcStreamRead(rkStream,m_fPortB);
    MgcStreamRead(rkStream,m_kLocation);
    MgcStreamRead(rkStream,m_kLeft);
    MgcStreamRead(rkStream,m_kUp);
    MgcStreamRead(rkStream,m_kDirection);

    MgcStreamRead(rkStream,m_uiPlaneQuantity);
    for (unsigned int uiP = 0; uiP < m_uiPlaneQuantity; uiP++)
        MgcStreamRead(rkStream,m_akWorldPlane[uiP]);
}
//---------------------------------------------------------------------------
void MgcCamera::Link (MgcStream& rkStream, MgcStream::Link* pkLink)
{
    MgcObject::Link(rkStream,pkLink);
}
//---------------------------------------------------------------------------
bool MgcCamera::Register (MgcStream& rkStream)
{
    return MgcObject::Register(rkStream);
}
//---------------------------------------------------------------------------
void MgcCamera::Save (MgcStream& rkStream)
{
    MgcObject::Save(rkStream);

    // native data
    MgcStreamWrite(rkStream,m_fFrustumN);
    MgcStreamWrite(rkStream,m_fFrustumF);
    MgcStreamWrite(rkStream,m_fFrustumL);
    MgcStreamWrite(rkStream,m_fFrustumR);
    MgcStreamWrite(rkStream,m_fFrustumT);
    MgcStreamWrite(rkStream,m_fFrustumB);
    MgcStreamWrite(rkStream,m_fPortL);
    MgcStreamWrite(rkStream,m_fPortR);
    MgcStreamWrite(rkStream,m_fPortT);
    MgcStreamWrite(rkStream,m_fPortB);
    MgcStreamWrite(rkStream,m_kLocation);
    MgcStreamWrite(rkStream,m_kLeft);
    MgcStreamWrite(rkStream,m_kUp);
    MgcStreamWrite(rkStream,m_kDirection);

    MgcStreamWrite(rkStream,m_uiPlaneQuantity);
    for (unsigned int uiP = 0; uiP < m_uiPlaneQuantity; uiP++)
        MgcStreamWrite(rkStream,m_akWorldPlane[uiP]);
}
//---------------------------------------------------------------------------
